summaryrefslogtreecommitdiff
path: root/app/[lng]
diff options
context:
space:
mode:
Diffstat (limited to 'app/[lng]')
-rw-r--r--app/[lng]/evcp/(evcp)/esg-check-list/page.tsx74
-rw-r--r--app/[lng]/evcp/(evcp)/evaluation-target-list/page.tsx172
-rw-r--r--app/[lng]/evcp/(evcp)/incoterms/page.tsx53
-rw-r--r--app/[lng]/evcp/(evcp)/payment-conditions/page.tsx53
-rw-r--r--app/[lng]/evcp/(evcp)/vendor-check-list/page.tsx74
-rw-r--r--app/[lng]/partners/(partners)/evaluation/page.tsx135
-rw-r--r--app/[lng]/partners/(partners)/rfq-ship/[id]/page.tsx2
7 files changed, 562 insertions, 1 deletions
diff --git a/app/[lng]/evcp/(evcp)/esg-check-list/page.tsx b/app/[lng]/evcp/(evcp)/esg-check-list/page.tsx
new file mode 100644
index 00000000..515751d5
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/esg-check-list/page.tsx
@@ -0,0 +1,74 @@
+import * as React from "react"
+import { type SearchParams } from "@/types/table"
+
+import { getValidFilters } from "@/lib/data-table"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+import { getEsgEvaluations } from "@/lib/esg-check-list/service"
+import { getEsgEvaluationsSchema } from "@/lib/esg-check-list/validation"
+import { EsgEvaluationsTable } from "@/lib/esg-check-list/table/esg-table"
+
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+ const searchParams = await props.searchParams
+ const search = getEsgEvaluationsSchema.parse(searchParams)
+
+ const validFilters = getValidFilters(search.filters)
+
+ const promises = Promise.all([
+ getEsgEvaluations({
+ ...search,
+ filters: validFilters,
+ }),
+
+ ])
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ ESG 자가진단표
+ </h2>
+ <p className="text-muted-foreground">
+ 협력업체 평가에 사용되는 ESG 자가진단표를 관리{" "}
+ {/* <span className="inline-flex items-center whitespace-nowrap">
+ <Ellipsis className="size-3" />
+ <span className="ml-1">버튼</span>
+ </span>
+ 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */}
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="ml-auto w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ /> */}
+ </React.Suspense>
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <EsgEvaluationsTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ )
+}
diff --git a/app/[lng]/evcp/(evcp)/evaluation-target-list/page.tsx b/app/[lng]/evcp/(evcp)/evaluation-target-list/page.tsx
new file mode 100644
index 00000000..d60f695a
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/evaluation-target-list/page.tsx
@@ -0,0 +1,172 @@
+import * as React from "react"
+import { Metadata } from "next"
+import { type SearchParams } from "@/types/table"
+import { getValidFilters } from "@/lib/data-table"
+import { Shell } from "@/components/shell"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { HelpCircle } from "lucide-react"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover"
+import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/ui/badge"
+
+import { getDefaultEvaluationYear, searchParamsEvaluationTargetsCache } from "@/lib/evaluation-target-list/validation"
+import { getEvaluationTargets } from "@/lib/evaluation-target-list/service"
+import { EvaluationTargetsTable } from "@/lib/evaluation-target-list/table/evaluation-target-table"
+
+export const metadata: Metadata = {
+ title: "협력업체 평가 대상 확정",
+ description: "협력업체 평가 대상을 확정하고 담당자를 지정합니다.",
+}
+
+interface EvaluationTargetsPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+// 프로세스 안내 팝오버 컴포넌트
+function ProcessGuidePopover() {
+ return (
+ <Popover>
+ <PopoverTrigger asChild>
+ <Button variant="ghost" size="icon" className="h-6 w-6">
+ <HelpCircle className="h-4 w-4 text-muted-foreground" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent className="w-96" align="start">
+ <div className="space-y-3">
+ <div className="space-y-1">
+ <h4 className="font-medium">평가 대상 확정 프로세스</h4>
+ <p className="text-sm text-muted-foreground">
+ 발주실적을 기반으로 평가 대상을 확정하는 절차입니다.
+ </p>
+ </div>
+ <div className="space-y-3 text-sm">
+ <div className="flex gap-3">
+ <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600">
+ 1
+ </div>
+ <div>
+ <p className="font-medium">발주실적 기반 자동 추출</p>
+ <p className="text-muted-foreground">전년도 10월 ~ 해당년도 9월 발주실적에서 업체 목록을 자동으로 생성합니다.</p>
+ </div>
+ </div>
+ <div className="flex gap-3">
+ <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600">
+ 2
+ </div>
+ <div>
+ <p className="font-medium">담당자 지정</p>
+ <p className="text-muted-foreground">각 평가 대상별로 5개 부서(발주/조달/품질/설계/CS)의 담당자를 지정합니다.</p>
+ </div>
+ </div>
+ <div className="flex gap-3">
+ <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600">
+ 3
+ </div>
+ <div>
+ <p className="font-medium">검토 및 의견 수렴</p>
+ <p className="text-muted-foreground">모든 담당자가 평가 대상 적합성을 검토하고 의견을 제출합니다.</p>
+ </div>
+ </div>
+ <div className="flex gap-3">
+ <div className="flex h-6 w-6 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600">
+ 4
+ </div>
+ <div>
+ <p className="font-medium">최종 확정</p>
+ <p className="text-muted-foreground">모든 담당자 의견이 일치하면 평가 대상으로 최종 확정됩니다.</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </PopoverContent>
+ </Popover>
+ )
+}
+
+export default async function EvaluationTargetsPage(props: EvaluationTargetsPageProps) {
+ const searchParams = await props.searchParams
+ const search = searchParamsEvaluationTargetsCache.parse(searchParams)
+ const validFilters = getValidFilters(search.filters)
+
+ // 기본 필터 처리 (통일된 이름 사용)
+ let basicFilters = []
+ if (search.basicFilters && search.basicFilters.length > 0) {
+ basicFilters = search.basicFilters
+ console.log("Using search.basicFilters:", basicFilters);
+ } else {
+ console.log("No basic filters found");
+ }
+
+ // 모든 필터를 합쳐서 처리
+ const allFilters = [...validFilters, ...basicFilters]
+
+ // 조인 연산자도 통일된 이름 사용
+ const joinOperator = search.basicJoinOperator || search.joinOperator || 'and';
+
+ // 현재 평가년도 (필터에서 가져오거나 기본값 사용)
+ const currentEvaluationYear = search.evaluationYear || getDefaultEvaluationYear()
+
+ // Promise.all로 감싸서 전달
+ const promises = Promise.all([
+ getEvaluationTargets({
+ ...search,
+ filters: allFilters,
+ joinOperator,
+ })
+ ])
+
+ return (
+ <Shell className="gap-4">
+ {/* 간소화된 헤더 */}
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center gap-2">
+ <h2 className="text-2xl font-bold tracking-tight">
+ 협력업체 평가 대상 확정
+ </h2>
+ <Badge variant="outline" className="text-sm">
+ {currentEvaluationYear}년도
+ </Badge>
+ <ProcessGuidePopover />
+ </div>
+ </div>
+ </div>
+
+ {/* 메인 테이블 (통계는 테이블 내부로 이동) */}
+ <React.Suspense
+ key={JSON.stringify(searchParams)} // URL 파라미터가 변경될 때마다 강제 리렌더링
+ fallback={
+ <DataTableSkeleton
+ columnCount={12}
+ searchableColumnCount={2}
+ filterableColumnCount={6}
+ cellWidths={[
+ "3rem", // checkbox
+ "5rem", // 평가년도
+ "4rem", // 구분
+ "8rem", // 벤더코드
+ "12rem", // 벤더명
+ "4rem", // 내외자
+ "6rem", // 자재구분
+ "5rem", // 상태
+ "5rem", // 의견일치
+ "8rem", // 담당자현황
+ "10rem", // 관리자의견
+ "8rem" // actions
+ ]}
+ shrinkZero
+ />
+ }
+ >
+ <EvaluationTargetsTable
+ promises={promises}
+ evaluationYear={currentEvaluationYear}
+ />
+ </React.Suspense>
+ </Shell>
+ )
+} \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/incoterms/page.tsx b/app/[lng]/evcp/(evcp)/incoterms/page.tsx
new file mode 100644
index 00000000..57a19009
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/incoterms/page.tsx
@@ -0,0 +1,53 @@
+import * as React from "react";
+import { type SearchParams } from "@/types/table";
+import { getValidFilters } from "@/lib/data-table";
+import { Shell } from "@/components/shell";
+import { Skeleton } from "@/components/ui/skeleton";
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton";
+import { SearchParamsCache } from "@/lib/incoterms/validations";
+import { getIncoterms } from "@/lib/incoterms/service";
+import { IncotermsTable } from "@/lib/incoterms/table/incoterms-table";
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>;
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+ const searchParams = await props.searchParams;
+ const search = SearchParamsCache.parse(searchParams);
+ const validFilters = getValidFilters(search.filters);
+
+ const promises = Promise.all([
+ getIncoterms({
+ ...search,
+ filters: validFilters,
+ }),
+ ]);
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">인코텀즈 관리</h2>
+ <p className="text-muted-foreground">
+ 인코텀즈(Incoterms)를 등록, 수정, 삭제할 수 있습니다.
+ </p>
+ </div>
+ </div>
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}></React.Suspense>
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={4}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <IncotermsTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ );
+} \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/payment-conditions/page.tsx b/app/[lng]/evcp/(evcp)/payment-conditions/page.tsx
new file mode 100644
index 00000000..b9aedfbb
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/payment-conditions/page.tsx
@@ -0,0 +1,53 @@
+import * as React from "react";
+import { type SearchParams } from "@/types/table";
+import { getValidFilters } from "@/lib/data-table";
+import { Shell } from "@/components/shell";
+import { Skeleton } from "@/components/ui/skeleton";
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton";
+import { SearchParamsCache } from "@/lib/payment-terms/validations";
+import { getPaymentTerms } from "@/lib/payment-terms/service";
+import { PaymentTermsTable } from "@/lib/payment-terms/table/payment-terms-table";
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>;
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+ const searchParams = await props.searchParams;
+ const search = SearchParamsCache.parse(searchParams);
+ const validFilters = getValidFilters(search.filters);
+
+ const promises = Promise.all([
+ getPaymentTerms({
+ ...search,
+ filters: validFilters,
+ }),
+ ]);
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">결제 조건 관리</h2>
+ <p className="text-muted-foreground">
+ 결제 조건(Payment Terms)을 등록, 수정, 삭제할 수 있습니다.
+ </p>
+ </div>
+ </div>
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}></React.Suspense>
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={4}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <PaymentTermsTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ );
+} \ No newline at end of file
diff --git a/app/[lng]/evcp/(evcp)/vendor-check-list/page.tsx b/app/[lng]/evcp/(evcp)/vendor-check-list/page.tsx
new file mode 100644
index 00000000..3fd7e425
--- /dev/null
+++ b/app/[lng]/evcp/(evcp)/vendor-check-list/page.tsx
@@ -0,0 +1,74 @@
+import * as React from "react"
+import { type SearchParams } from "@/types/table"
+
+import { getValidFilters } from "@/lib/data-table"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+import { getGenralEvaluationsSchema } from "@/lib/general-check-list/validation"
+import { GeneralEvaluationsTable } from "@/lib/general-check-list/table/general-check-list-table"
+import { getGeneralEvaluations } from "@/lib/general-check-list/service"
+
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+ const searchParams = await props.searchParams
+ const search = getGenralEvaluationsSchema.parse(searchParams)
+
+ const validFilters = getValidFilters(search.filters)
+
+ const promises = Promise.all([
+ getGeneralEvaluations({
+ ...search,
+ filters: validFilters,
+ }),
+
+ ])
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 협력업체 정기평가 체크리스트
+ </h2>
+ <p className="text-muted-foreground">
+ 협력업체 평가에 사용되는 정기평가 체크리스트를 관리{" "}
+ {/* <span className="inline-flex items-center whitespace-nowrap">
+ <Ellipsis className="size-3" />
+ <span className="ml-1">버튼</span>
+ </span>
+ 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */}
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* <DateRangePicker
+ triggerSize="sm"
+ triggerClassName="ml-auto w-56 sm:w-60"
+ align="end"
+ shallow={false}
+ /> */}
+ </React.Suspense>
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <GeneralEvaluationsTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ )
+}
diff --git a/app/[lng]/partners/(partners)/evaluation/page.tsx b/app/[lng]/partners/(partners)/evaluation/page.tsx
new file mode 100644
index 00000000..dc1d4137
--- /dev/null
+++ b/app/[lng]/partners/(partners)/evaluation/page.tsx
@@ -0,0 +1,135 @@
+import * as React from "react"
+import { type SearchParams } from "@/types/table"
+import { getValidFilters } from "@/lib/data-table"
+import { Skeleton } from "@/components/ui/skeleton"
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
+import { Shell } from "@/components/shell"
+import { getServerSession } from "next-auth"
+import { authOptions } from "@/app/api/auth/[...nextauth]/route"
+import Link from "next/link"
+import { Button } from "@/components/ui/button"
+import { LogIn } from "lucide-react"
+import { getEvaluationSubmissions } from "@/lib/vendor-evaluation-submit/service"
+import { getEvaluationsSubmitSchema } from "@/lib/vendor-evaluation-submit/validation"
+import { EvaluationSubmissionsTable } from "@/lib/vendor-evaluation-submit/table/submit-table"
+
+interface IndexPageProps {
+ searchParams: Promise<SearchParams>
+}
+
+export default async function IndexPage(props: IndexPageProps) {
+ const searchParams = await props.searchParams
+ const search = getEvaluationsSubmitSchema.parse(searchParams)
+ const validFilters = getValidFilters(search.filters)
+
+ // Get session
+ const session = await getServerSession(authOptions)
+
+ // Check if user is logged in
+ if (!session || !session.user) {
+ // Return login required UI instead of redirecting
+ return (
+ <Shell className="gap-6">
+ <div className="flex items-center justify-between">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 정기평가
+ </h2>
+ <p className="text-muted-foreground">
+ 요청된 정기평가를 입력하고 제출할 수 있습니다.
+ </p>
+ </div>
+ </div>
+
+ <div className="flex flex-col items-center justify-center py-12 text-center">
+ <div className="rounded-lg border border-dashed p-10 shadow-sm">
+ <h3 className="mb-2 text-xl font-semibold">로그인이 필요합니다</h3>
+ <p className="mb-6 text-muted-foreground">
+ 정기평가를 확인하려면 먼저 로그인하세요.
+ </p>
+ <Button size="lg" asChild>
+ <Link href="/partners">
+ <LogIn className="mr-2 h-4 w-4" />
+ 로그인하기
+ </Link>
+ </Button>
+ </div>
+ </div>
+ </Shell>
+ )
+ }
+
+ // User is logged in, proceed with vendor ID
+ const vendorId = session.user.companyId
+
+ // Validate vendorId (should be a number)
+ const idAsNumber = Number(vendorId)
+
+ console.log(idAsNumber)
+
+ if (isNaN(idAsNumber)) {
+ // Handle invalid vendor ID (this shouldn't happen if authentication is working properly)
+ return (
+ <Shell className="gap-6">
+ <div className="flex items-center justify-between">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 정기평가
+ </h2>
+ </div>
+ </div>
+ <div className="flex flex-col items-center justify-center py-12 text-center">
+ <div className="rounded-lg border border-dashed p-10 shadow-sm">
+ <h3 className="mb-2 text-xl font-semibold">계정 오류</h3>
+ <p className="mb-6 text-muted-foreground">
+ 업체 정보가 올바르게 설정되지 않았습니다. 관리자에게 문의하세요.
+ </p>
+ </div>
+ </div>
+ </Shell>
+ )
+ }
+
+ // If we got here, we have a valid vendor ID
+ const promises = Promise.all([
+ getEvaluationSubmissions({
+ ...search,
+ filters: validFilters,
+ }, idAsNumber)
+ ])
+
+ return (
+ <Shell className="gap-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div className="flex items-center justify-between space-y-2">
+ <div>
+ <h2 className="text-2xl font-bold tracking-tight">
+ 정기평가
+ </h2>
+ <p className="text-muted-foreground">
+ 요청된 정기평가를 입력하고 제출할 수 있습니다.
+ </p>
+ </div>
+ </div>
+ </div>
+
+ <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
+ {/* DateRangePicker can go here */}
+ </React.Suspense>
+
+ <React.Suspense
+ fallback={
+ <DataTableSkeleton
+ columnCount={6}
+ searchableColumnCount={1}
+ filterableColumnCount={2}
+ cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
+ shrinkZero
+ />
+ }
+ >
+ <EvaluationSubmissionsTable promises={promises} />
+ </React.Suspense>
+ </Shell>
+ )
+} \ No newline at end of file
diff --git a/app/[lng]/partners/(partners)/rfq-ship/[id]/page.tsx b/app/[lng]/partners/(partners)/rfq-ship/[id]/page.tsx
index 772a9840..7f5400f3 100644
--- a/app/[lng]/partners/(partners)/rfq-ship/[id]/page.tsx
+++ b/app/[lng]/partners/(partners)/rfq-ship/[id]/page.tsx
@@ -50,7 +50,7 @@ export default async function VendorQuotationPage({ params }: PageProps) {
with: {
rfq: true, // 관계 설정 필요
vendor: true, // 관계 설정 필요
- // items: true, // 관계 설정 필요
+ items: true, // 관계 설정 필요
}
})